/* implememtacja biblioteki wykorzystujaca dosatwep do pamieci poprzez
 * mapowanie pliku /dev/mem
 * 
 * autor: maciek gajewski
 *  data: 14.02.2005
 *
 * wykorzystuje kod z devmem2.c
 */

/** @file libgpiop.c implementation of userspace gpio access library */
 
  
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <asm/types.h>
#include <time.h>

/** @internal taken from hardware.h, but eventually modified and actually unused */ 
#define io_p2v(x) (x)
#define io_v2p(x) (0x3c000000 + ((x) & 0x01ffffff) + (((x) & 0x0e000000) << 1))


/** @internal local version of __REG, translates register address into mapped memory addr */
#define __REG(x)	(*(volatile __u32 *)(base + (x & MAP_MASK) )) 

/*% form system headers */
#define __REG2(x,y)     (*(volatile __u32 *)(  ( (__u32)(&__REG(x)) + (y) ) ) )

// include registers from system header
#include "asm-arm/arch-pxa/pxa-regs.h"

#include "libgpio.h"



/** @internal mapped memory base address*/
volatile static void* base = (void*)-1;
/** @internal /dev/mem file handler */
static int fd = -1;


#define MAP_SIZE 4096UL
#define MAP_MASK (MAP_SIZE - 1)
#define MAP_BASE (io_p2v(0x40E00000) & ~MAP_MASK)


/* ------------------------- gpio init ---------------------------- */
int gpio_init()
{
	if ( fd == -1 )
	{
			/*open file */
		if( (fd = open("/dev/mem", O_RDWR | O_SYNC)) == -1){
			perror("couldn't open /dev/mem");
			return -1;
		}
			/* map memory */
		base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, MAP_BASE);
		if(base == (void*)-1){
			perror("couldn't map memory");
			return -1;
		}
		
		return 0;
	}
	else
	{
		fprintf(stderr, "libgpio:gpio_init: already initilaized\n");
		return 1; 
	}
}

/* ---------------------------- gpio_shutdown ---------------------- */
int gpio_shutdown()
{
	if(fd!=0){
		close(fd);
	}
	
	fd = -1;
	base = (void*)-1;
	
	return 0;
}

/* ---------------------- set mode --------------------- */
/** heleper procedre setting 2-bit line function
	@param line gpiop line number
	@param function 0:gpio, 1-3:AF1-3 
	@internal*/
inline void gpio_set_function(int line, int function)
{
	int gafr;
	gafr = GAFR(line) & ~(0x3 << (((line) & 0xf)*2));
	GAFR(line) = gafr |  (function  << (((line) & 0xf)*2));
}

/* -------------------------- gpio_set --------------------------------- */
int gpio_set(int line, gpio_state* state)
{
	__u32 uline;
	uline = line;
	
	if(base == (void*)-1){
		return -1;
	}
	
		/* direction */
	if(state->direction == LG_GPIO_OUT){
		GPDR(uline) |= GPIO_bit(uline);
	}
	else if(state->direction == LG_GPIO_IN){
		GPDR(uline) &= ~GPIO_bit(uline);
	}
		/* function */
	if ( state->function != LG_FN_DONTCARE) {
		gpio_set_function(uline, state->function);
	}
		
		/* level */
	if(state->state == 1){
		GPSR(uline) = GPIO_bit(uline);
	}
	else if ( state->state == 0 ){
		GPCR(uline) = GPIO_bit(uline);
	}
	
	return 0;
}

/* -------------------------- gpio_get --------------------------------- */
int gpio_get(int line, gpio_state* state)
{
	__u32 af;
	__u32 uline;
	
	uline = line;
	
	if(base == (void*)-1){
		return -1;
	}
	
		/* read function */
	af = ((GAFR(uline) >> ((uline & 0x0f) << 0x01)) & 0x03);
	state->function = af;
		/* direction */
	state->direction = (GPDR(uline) & GPIO_bit(uline)) ? LG_GPIO_OUT : LG_GPIO_IN;
		/* level */
	state->state = (GPLR(uline) & GPIO_bit(uline)) ? 1 : 0;
		
	return 0;
}
/* --------------------------------------------------------------------------------- */
/* --------------------------- userland bus implementation ------------------------- */
/* --------------------------------------------------------------------------------- */


/** @internal bus data, mostly cached register adresses and bit masks */
struct _gpio_bus {
	volatile __u32* direction[LG_BUS_DATALINES];	/**< data line direction registers */
	volatile __u32* read[LG_BUS_DATALINES];		/**< data line level registers */
	volatile __u32* set[LG_BUS_DATALINES];			/**< data line set registers */
	volatile __u32* clear[LG_BUS_DATALINES];		/**< data line clear registers */
	__u32  bit[LG_BUS_DATALINES];					/**< data line bit masks */
	
	int addrnum;					/**< address line width (max 8) */
	volatile __u32* aset[LG_BUS_MAX_ADDRLINES];		/**< addr line set registers */
	volatile __u32* aclear[LG_BUS_MAX_ADDRLINES];		/**< addr line clear registers */
	__u32  abit[LG_BUS_MAX_ADDRLINES];					/**< address line bit masks */
	
	__u32 wrbit;					/**< wr bitmask */
	__u32 rdbit;					/**< rd bitmask */
	__u32 csbit;					/**< rd bitmask */
	volatile __u32* rdset;			/**< rd set register */
	volatile __u32* wrset;			/**< wr set register */
	volatile __u32* csset;			/**< wr set register */
	volatile __u32* rdclear;			/**< rd clear register */
	volatile __u32* wrclear;			/**< wr clear register */
	volatile __u32* csclear;			/**< wr clear register */
};
	

/* ----------------------------- bus create ------------------------------- */
gpio_bus gpio_bus_create(int data[8], int addrnum, int* addr, int wr, int rd, int cs)
{
	struct _gpio_bus* channel;
	int i;
		/* libgpipo initialized? */
	if(base == (void*)-1){
		return NULL;
	}
	
		/* allocation */
	channel = (struct _gpio_bus*)malloc(sizeof(struct _gpio_bus));
	if(!channel){
		fprintf(stderr, "couldn't allocate memory for bus\n");
		return NULL;
	}
		/* init */
		
		/* data line */
	for(i=0; i<8; i++){
			/* register caching� */
		channel->direction[i] = &GPDR(data[i]);
		channel->read[i] = &GPLR(data[i]);
		channel->set[i] = &GPSR(data[i]);
		channel->clear[i] = &GPCR(data[i]);
		channel->bit[i] = GPIO_bit(data[i]);
			/* setting lines in gpio mode */
		gpio_set_function(data[i], LG_FN_GPIO);
	}
		/* addr line */
	for(i=0; i<addrnum; i++){
			/* register caching� */
		channel->aset[i] = &GPSR(addr[i]);
		channel->aclear[i] = &GPCR(addr[i]);
		channel->abit[i] = GPIO_bit(addr[i]);
			/* setting lines in gpio mode */
		gpio_set_function(addr[i], LG_FN_GPIO);
			/* setting line in output mode */
		GPDR(addr[i]) |= channel->abit[i];
	}
		/* rd and wr lines */
	channel->rdset = &GPSR(rd);
	channel->wrset = &GPSR(wr);
	channel->csset = &GPSR(cs);
	channel->rdclear = &GPCR(rd);
	channel->wrclear = &GPCR(wr);
	channel->csclear = &GPCR(cs);
	channel->wrbit = GPIO_bit(wr);
	channel->rdbit = GPIO_bit(rd);
	channel->csbit = GPIO_bit(cs);
		// control lines in GPIO function
	gpio_set_function(rd, LG_FN_GPIO);
	gpio_set_function(wr, LG_FN_GPIO);
	gpio_set_function(cs, LG_FN_GPIO);
		// control lines in OUT mode
	GPDR(wr) |= channel->wrbit;
	GPDR(rd) |= channel->rdbit;
	GPDR(cs) |= channel->csbit;
		// control lines in inactiv (logig-high)
	*channel->rdset = channel->rdbit;
	*channel->wrset = channel->wrbit;
	*channel->csset = channel->csbit;
	
	return (gpio_bus)channel;	
	
}

/* ---------------------------------- bus close ---------------------------- */
void gpio_bus_close(gpio_bus bus)
{
	if(bus){
		free(bus);
	}
}

/* --------------------------------- bus write ------------------------------- */
int gpio_bus_write(gpio_bus handler, char addr, char data)
{
	struct _gpio_bus* bus;
	int i, mask;
	struct timespec gap;
	
		/* libgpio initialized? */
	if(base == (void*)-1){
		return -1;
	}
		/* bus open? */
	if(!handler){
		return -1;
	}
	
	bus = (struct _gpio_bus*)handler;
	
		/*settnig rd and wr (just in case) */
	*bus->rdset = bus->rdbit;
	*bus->wrset = bus->wrbit;
	*bus->csset = bus->csbit;
	
		/* setting addr line */
	mask = 1;
	for(i=0; i < bus->addrnum; i++){
		if(addr & mask){
			*bus->aset[i] = bus->abit[i];
		}
		else{
			*bus->aclear[i] = bus->abit[i];
		}
		
		mask <<= 1;
	}
		/* setting data line */
	mask = 1;
	for(i=0; i<8; i++){
			/* direction: out */
		*bus->direction[i] |= bus->bit[i];
			/* level */
		if(data & mask){
			*bus->set[i] = bus->bit[i];
		}
		else{
			*bus->clear[i] = bus->bit[i];
		}
		
		mask <<= 1;
	}
		/* chip select */
	*bus->csclear = bus->csbit;
		/* ok, now clear wr for a while */
	*bus->wrclear = bus->wrbit;
	
		/* wait 50 ns */
	gap.tv_sec = 0;
	gap.tv_nsec = 50;
	nanosleep(&gap, NULL);
		
		/* bring wr back up */
	*bus->wrset = bus->wrbit;
		/* chip un-select */
	*bus->csset = bus->csbit;

	return 0;	
}

/* ------------------------------------ bus read ---------------------------------- */
int gpio_bus_read(gpio_bus handler, char addr)
{
	struct _gpio_bus* bus;
	int i, mask;
	char data;
	
		/* libgpio initialized? */
	if(base == (void*)-1){
		return -1;
	}
		/* bus open? */
	if(!handler){
		return -1;
	}
	
	bus = (struct _gpio_bus*)handler;
	
		/* settnig rd and wr (just in case) */
	*bus->rdset = bus->rdbit;
	*bus->wrset = bus->wrbit;
	*bus->csset = bus->csbit;
	
		/* setting addr */
	mask = 1;
	for(i=0; i < bus->addrnum; i++){
		if(addr & mask){
			*bus->aset[i] = bus->abit[i];
		}
		else{
			*bus->aclear[i] = bus->abit[i];
		}

		mask <<= 1;
	}
	
		/* set data line into 'input' mode  */
	for(i=0; i<8; i++){
			/* direction */
		*bus->direction[i] &= ~bus->bit[i];
	}	
		
		/* chip select */
	*bus->csclear = bus->csbit;
		/* clear rd for reading*/
	*bus->rdclear = bus->rdbit;
	
		/* read data */
	mask = 1; /* this pauses for few ns, letting device to read addr ans set data */
	data = 0;
	for(i=0; i<8; i++){
		
		if(*bus->read[i] & bus->bit[i]){
			data |= mask;
		}
	
		mask <<= 1;
	}
	
		/* bring rd up */
	*bus->rdset = bus->rdbit;
	
	return data;
}
/* ------------------------------------ bus multi read ---------------------------------- */
int gpio_bus_multiread(gpio_bus handler, char addr, char* buf, int size)
{
	struct _gpio_bus* bus;
	int i,b, mask;
	char *data;
	
		/* libgpio initialized? */
	if(base == (void*)-1){
		return -1;
	}
		/* bus open? */
	if(!handler){
		return -1;
	}
	
	bus = (struct _gpio_bus*)handler;
	
		/* settnig rd and wr (just in case) */
	*bus->rdset = bus->rdbit;
	*bus->wrset = bus->wrbit;
	*bus->csset = bus->csbit;
	
		/* setting addr */
	mask = 1;
	for(i=0; i < bus->addrnum; i++){
		if(addr & mask){
			*bus->aset[i] = bus->abit[i];
		}
		else{
			*bus->aclear[i] = bus->abit[i];
		}
			/* kierunek */
		mask <<= 1;
	}
	
		/* data line in 'input' mode */
	for(i=0; i<8; i++){
			/* direction */
		*bus->direction[i] &= ~bus->bit[i];
	}
	
		/* enable chip */	
	bus->csclear = bus->csbit;
	
		/* read data into buffer */
	data = buf;
	for(i=0; i<size; i++){	
			/* ok, rd down� (on)*/
		//*bus->rdclear = bus->rdbit;
	
			/* read data */
		mask = 1; /* pause */
		*data = 0;
		for(b=0; b<8; b++){
			
			if(*bus->read[b] & bus->bit[b]){
				*data |= mask;
			}
		
			mask <<= 1;
		}
	
			/* rd up (off) */
		*bus->rdset = bus->rdbit;
		data ++; /* pause */
	}
		
		/* disable chip */	
	bus->csset = bus->csbit;
	
	return size;
}


